`timescale 1ns / 1ps
`include"define.sv"

module tb_tpu_top;

//-------------------- 时钟与复位 --------------------
reg clk;
reg reset;

//-------------------- APB总线信号 --------------------
reg PSEL, PENABLE, PWRITE;
reg [31:0] PADDR;
reg [31:0] PWDATA;
wire [31:0] PRDATA;
wire PREADY, PSLVERR;

//-------------------- AXI信号 --------------------
reg [`AXI_ADDR_WIDTH-1:0] s_AWADDR;
reg [7:0] s_AWLEN;
reg [2:0] s_AWSIZE;
reg [1:0] s_AWBURST;
reg s_AWVALID;
wire s_AWREADY;

reg [`AXI_DATA_WIDTH-1:0] s_WDATA;
reg [`AXI_DATA_WIDTH/8-1:0] s_WSTRB;
reg s_WLAST;
reg s_WVALID;
wire s_WREADY;

wire [1:0] s_BRESP;
wire s_BVALID;
reg s_BREADY;

reg [`AXI_ADDR_WIDTH-1:0] s_ARADDR;
reg [7:0] s_ARLEN;
reg [2:0] s_ARSIZE;
reg [1:0] s_ARBURST;
reg s_ARVALID;
wire s_ARREADY;

wire [`AXI_DATA_WIDTH-1:0] s_RDATA;
wire [1:0] s_RRESP;
wire s_RLAST;
wire s_RVALID;
reg s_RREADY;

//-------------------- 文件数据存储 --------------------
reg [`AXI_DATA_WIDTH-1:0] file_data[1023:0]; // 存储文件数据
integer file_handle;
integer data_count;
reg [255:0] line_buffer; // 用于存储读取的行

//-------------------- 时钟/复位生成 --------------------
initial begin
    clk = 0;
    forever #5 clk = ~clk; // 100MHz
end

initial begin
    reset = 1;
    #100;
    reset = 0;
end

//-------------------- 文件数据加载（仿真用） --------------------
initial begin
    int fd;                  // 文件句柄
    int char;                // 读取的字符ASCII值
    string hex_str;          // 临时存储有效16进制字符
    int char_count;          // 有效字符计数
    int array_idx;           // 数据数组下标
    int total_chars;         // 文件总字符数

    hex_str = "";
    char_count = 0;
    array_idx = 0;
    total_chars = 0;

    fd = $fopen("E:/vivado_project/tpu_top/int8_bram_init.txt", "r");
    if (fd == 0) begin
        $display("Error: 无法打开文件 int8_bram_init.txt");
        $finish;
    end

    while (array_idx < 1024) begin
        char = $fgetc(fd);
        if (char == -1) begin
            $display("文件读取完毕，文件总字符数 %0d", total_chars);
            break;
        end
        total_chars++;

        // 判断是否为有效16进制字符
        if ((char >= "0" && char <= "9") ||
            (char >= "A" && char <= "F") ||
            (char >= "a" && char <= "f")) begin
            hex_str = {hex_str, char[7:0]};
            char_count++;
            if (char_count == 32) begin
                if ($sscanf(hex_str, "%h", file_data[array_idx]) != 1) begin
                    $display("Error: 数据转换失败，行号 %0d, 字符串: %s", array_idx, hex_str);
                    $finish;
                end
                // $display("array_idx=%0d, hex_str='%s', data=0x%032x", array_idx, hex_str, file_data[array_idx]);
                hex_str = "";
                char_count = 0;
                array_idx++;
            end
        end
        // 非16进制字符直接忽略
    end

    // 检查文件末尾是否有不足32字符的残留
    if (char_count > 0 && char_count < 32) begin
        $display("警告：文件末尾存在不足32字符的数据，已忽略");
    end

    $fclose(fd);

    $display("成功加载数据，共 %0d 个128位数据", array_idx);
    if (array_idx < 1024) begin
        $display("注意：文件实际数据 %0d 个16进制字符，%0d 个128位数据", 
                 array_idx * 32, array_idx);
    end
end

//-------------------- AXI读通道仿真 --------------------
reg [7:0] read_counter;
reg read_active;

always @(posedge clk or posedge reset) begin
    if (reset) begin
        read_counter <= 0;
        read_active <= 0;
    end else begin
        if (s_ARVALID && s_ARREADY) begin
            read_active <= 1;
            read_counter <= 0;
        end else if (read_active && s_RVALID && s_RREADY) begin
            if (read_counter == s_ARLEN) begin
                read_active <= 0;
            end else begin
                read_counter <= read_counter + 1;
            end
        end
    end
end

assign s_RDATA = file_data[s_ARADDR[11:0] + read_counter];
assign s_RVALID = read_active;
assign s_RLAST = (read_counter == s_ARLEN);
assign s_RRESP = 2'b00;
assign s_ARREADY = !read_active;

//-------------------- APB写任务 --------------------
task apb_write(input [15:0] addr, input [31:0] data);
begin
    @(negedge clk);
    PSEL   = 1;
    PENABLE= 0;
    PWRITE = 1;
    PADDR  = addr;
    PWDATA = data;
    @(negedge clk);
    PENABLE= 1;
    wait(PREADY);
    @(negedge clk);
    PSEL   = 0;
    PENABLE= 0;
end
endtask

//-------------------- AXI突发写任务 --------------------
task axi_burst_write(
    input [`AXI_ADDR_WIDTH-1:0] addr,
    input [7:0] len, // len=突发长度-1
    input [`AXI_DATA_WIDTH-1:0] data_array[]
);
    integer i;
    begin
        // 写地址通道
        @(negedge clk);
        s_AWADDR  = addr;
        s_AWLEN   = len;
        s_AWSIZE  = 3'd4; // 128bit=16B=2^4
        s_AWBURST = 2'b01; // INCR
        s_AWVALID = 1;
        wait(s_AWREADY);
        @(negedge clk);
        s_AWVALID = 0;

        // 写数据通道
        for (i = 0; i <= len; i = i + 1) begin
            s_WDATA = data_array[i];
            s_WSTRB = 16'hFFFF;
            s_WLAST = (i == len);
            s_WVALID = 1;
            wait(s_WREADY);
            @(negedge clk);
            s_WVALID = 0;
        end

        // 写响应通道
        s_BREADY = 1;
        wait(s_BVALID);
        @(negedge clk);
        s_BREADY = 0;
    end
endtask

//-------------------- DUT实例化 --------------------
// AXI master写通道相关wire
wire [31:0] M_AXI_AWADDR;
wire        M_AXI_AWVALID;
wire [31:0] M_AXI_WDATA;
wire [3:0]  M_AXI_WSTRB;
wire        M_AXI_WVALID;
wire        M_AXI_BREADY;
wire        axi_done;

tpu_top dut(
    .clk(clk),
    .reset(reset),
    // AXI slave
    .s_AWADDR(s_AWADDR),
    .s_AWLEN(s_AWLEN),
    .s_AWSIZE(s_AWSIZE),
    .s_AWBURST(s_AWBURST),
    .s_AWVALID(s_AWVALID),
    .s_AWREADY(s_AWREADY),
    .s_WDATA(s_WDATA),
    .s_WSTRB(s_WSTRB),
    .s_WLAST(s_WLAST),
    .s_WVALID(s_WVALID),
    .s_WREADY(s_WREADY),
    .s_BRESP(s_BRESP),
    .s_BVALID(s_BVALID),
    .s_BREADY(s_BREADY),
    // APB
    .PSEL(PSEL),
    .PENABLE(PENABLE),
    .PWRITE(PWRITE),
    .PADDR(PADDR),
    .PWDATA(PWDATA),
    .PRDATA(PRDATA),
    .PREADY(PREADY),
    .PSLVERR(PSLVERR),
    // AXI master
    .M_AXI_AWADDR(M_AXI_AWADDR),
    .M_AXI_AWVALID(M_AXI_AWVALID),
    .M_AXI_WDATA(M_AXI_WDATA),
    .M_AXI_WSTRB(M_AXI_WSTRB),
    .M_AXI_WVALID(M_AXI_WVALID),
    .M_AXI_BREADY(M_AXI_BREADY),
    .axi_done(axi_done)
);

//-------------------- 仿真流程 --------------------
initial begin
    // APB配置
    PSEL = 0; PENABLE = 0; PWRITE = 0; PADDR = 0; PWDATA = 0;
    s_ARVALID = 0; s_RREADY = 0;
    s_AWVALID = 0; s_WVALID = 0; s_BREADY = 0;
    #200;
    apb_write(16'h0000, 32'h01); // matrix_type
    apb_write(16'h0004, 32'h01); // data_type
    apb_write(16'h0008, 32'h00); // mix_precision
    #100;
    // 读取数据文件
    $readmemh("int8_bram_init.txt", file_data);
    // AXI突发写入
    axi_burst_write(32'h0000_0000, 47, file_data); // 48组128位突发写
    #1000;
    $display("tb_tpu_top仿真结束");
    $finish;
end

//-------------------- 波形输出 --------------------
initial begin
    $dumpfile("tb_tpu_top.vcd");
    $dumpvars(0, tb_tpu_top);
end

endmodule